/*
 * Copyright (c) 2021 The red-star Project
 *
 * Licensed under the Apache License, version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.inyourcode.core.transport.session;
import com.inyourcode.core.util.StackTraceUtil;
import com.inyourcode.core.db.api.IDataContext;
import com.inyourcode.core.threads.api.HashExecutor;
import com.inyourcode.core.transport.session.api.MessageHolder;
import com.inyourcode.core.transport.session.api.Session;
import com.inyourcode.core.threads.ConsumerTask;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
 * session 基础实现
 * @author JackLei
 */
public class BasicSession implements Session {
    private static final Logger LOGGER = LoggerFactory.getLogger(BasicSession.class);
    public static final String ATTR_OPENID = "openId";
    private long id;
    private Channel channel;
    private IDataContext dataContext;
    private volatile boolean auth;
    private HashExecutor executor;
    private Map<String, Object> attrMap = new HashMap<String, Object>();

    public BasicSession() {
    }

    public void setChannel(Channel channel) {
        this.channel = channel;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Override
    public HashExecutor getExecutor() {
        return executor;
    }

    @Override
    public void setExecutor(HashExecutor executor) {
        this.executor = executor;
    }

    @Override
    public void execute(Runnable runnable) {
        this.executor.execute(getId().hashCode(), runnable);
    }

    public IDataContext getDataContext() {
        return dataContext;
    }

    public void setDataContext(IDataContext dataContext) {
        this.dataContext = dataContext;
    }

    @Override
    public Long getId() {
        return id;
    }

    @Override
    public Channel channel() {
        return channel;
    }

    @Override
    public boolean isActive() {
        return channel.isActive();
    }

    @Override
    public void write(MessageHolder messageHolder) {
        write(messageHolder, null);
    }

    @Override
    public void write(MessageHolder messageHolder, ChannelFutureListener listener) {
        if (messageHolder == null) {
            return;
        }

        if (listener != null) {
            channel.writeAndFlush(messageHolder).addListener(listener);
        } else {
            channel.writeAndFlush(messageHolder);
        }
    }

    @Override
    public void closeAndNotify(MessageHolder message) {
        write(message, future -> {
            if (future.isSuccess()) {
                Channel closeChannel = future.channel();
                closeChannel.close();
                LOGGER.info("channel close, old:{},new:{}", closeChannel, channel);
            }
        });
    }

    @Override
    public void flush() {
        channel.flush();
    }

    @Override
    public boolean isAuth() {
        return auth;
    }

    @Override
    public void setAuth(boolean auth) {
        this.auth = auth;
    }

    @Override
    public void replaceInfo(Session replaceInfo) {
        this.channel = replaceInfo.channel();
    }

    @Override
    public void loadAllData(HashExecutor dbExecutor, ConsumerTask consumerTask) {
        dbExecutor.crossExecute(getId().hashCode(),
                this,
                ()->{
                    cacheDataOptExpire(false, 0);
                    dataContext.loadAll();
                }
                ,
                consumerTask);
    }

    @Override
    public void saveAllData(HashExecutor dbExecutor, ConsumerTask callBack) {
        this.dataContext.saveAll(dbExecutor,this, callBack);
    }

    @Override
    public <T> T getContextData(Class<T> clazz) {
        return this.dataContext.getData(clazz);
    }

    @Override
    public <T> T getContextDataWhenNullCreate(Class<T> clazz) {
        T contextData = this.dataContext.getData(clazz);
        if (contextData == null) {
            try {
                contextData = clazz.newInstance();
                this.dataContext.putData(contextData);
            } catch (InstantiationException e) {
                LOGGER.error("{}", StackTraceUtil.stackTrace(e));
            } catch (IllegalAccessException e) {
                LOGGER.error("{}", StackTraceUtil.stackTrace(e));
            }
        }
        return contextData;
    }

    @Override
    public void putContextData(Object data) {
        this.dataContext.putData(data);
    }

    @Override
    public void setAttribute(String key, Object val) {
        this.attrMap.put(key, val);
    }

    @Override
    public Object getAttribute(String key) {
        return this.attrMap.get(key);
    }

    @Override
    public void cacheDataOptExpire(boolean clear, int expireTime) {
        this.dataContext.cacheDataOptExpire(clear, expireTime);
    }

    @Override
    public int state() {
        return this.dataContext.state();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BasicSession that = (BasicSession) o;
        return id == that.id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

}
